package battleship;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Collection;
import java.util.List;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTable;

/**
 *
 * @author kamil
 */
public class Battleship {

	MainFrame parent;
	Rules rules;
	Board GameBoard;
	JPanel infoPanel;
	BattleshipPanel battleshipPanel;
	Player currentPlayer;
	Player opponentPlayer;
	Grid pl1FireGrid; //is seen by player2
	Grid pl1OwnGrid; //is seen by player1
	Grid pl2FireGrid; //is seen by player1
	Grid pl2OwnGrid; //is seen by player2
	JTable pl1Ships;
	JTable pl2Ships;
	public boolean fireState;
	public boolean fired;
	public boolean pl1State;
	public boolean pl2State;
	public final static int fieldSize = 30;

	public Battleship(MainFrame parent, Rules rules) {
		this.parent = parent;
		this.rules = rules;
		this.GameBoard = (Board) parent.GameBoard;
		this.infoPanel = parent.infoPanel;
		this.currentPlayer = rules.player1;

		this.battleshipPanel = new BattleshipPanel();

		this.pl1OwnGrid = new Grid(rules.ySize, rules.xSize);
		this.pl2OwnGrid = new Grid(rules.ySize, rules.xSize);

		this.pl1FireGrid = new Grid(rules.ySize, rules.xSize);
		this.pl2FireGrid = new Grid(rules.ySize, rules.xSize);

		KeyAdapter keyAdapter = new KeyAdapter() {

			@Override
			public void keyPressed(KeyEvent evt) {
				switch (evt.getKeyCode()) {
					case KeyEvent.VK_ENTER: {
						battleshipPanel.ChangePlayerButton.doClick();
						break;
					}
					case KeyEvent.VK_LEFT:
					case KeyEvent.VK_RIGHT:{
						if(battleshipPanel.ChangeViewButton.isEnabled())
							battleshipPanel.ChangeViewButton.doClick();
						else
							battleshipPanel.ChangePlayerButton.doClick();
						break;
					}
				}
			}
		};

		pl1OwnGrid.addKeyListener(keyAdapter);
		pl2OwnGrid.addKeyListener(keyAdapter);
		pl1FireGrid.addKeyListener(keyAdapter);
		pl2FireGrid.addKeyListener(keyAdapter);
		battleshipPanel.addKeyListener(keyAdapter);
		battleshipPanel.ChangePlayerButton.addKeyListener(keyAdapter);
		battleshipPanel.ChangeViewButton.addKeyListener(keyAdapter);

		this.pl1OwnGrid.setBounds(10, 10, fieldSize * rules.xSize,
				fieldSize * rules.ySize);
		this.pl2OwnGrid.setBounds(10, 10, fieldSize * rules.xSize,
				fieldSize * rules.ySize);

		this.pl1FireGrid.setBounds(10, 10, fieldSize * rules.xSize,
				fieldSize * rules.ySize);
		this.pl2FireGrid.setBounds(10, 10, fieldSize * rules.xSize,
				fieldSize * rules.ySize);

		ActionListener fire = new FireListener();
		for (int i = 0; i < rules.ySize; i++) {
			for (int j = 0; j < rules.xSize; j++) {
				pl1FireGrid.fields[i][j].addActionListener(fire);
				pl2FireGrid.fields[i][j].addActionListener(fire);
			}
		}

		for (Ship s : rules.player1.getShips()) {
			for (Coords c : s.shipPoints) {
				pl1FireGrid.fields[c.y][c.x].coords = c;
				pl1OwnGrid.fields[c.y][c.x].coords = c;
				pl1OwnGrid.fields[c.y][c.x].changeType(FieldType.SHIP);
				pl1OwnGrid.fields[c.y][c.x].setToolTipText(
						pl1OwnGrid.fields[c.y][c.x].coords.owner.type
						+ ": " + c);
			}
		}
		for (Ship s : rules.player2.getShips()) {
			for (Coords c : s.shipPoints) {
				pl2FireGrid.fields[c.y][c.x].coords = c;
				pl2OwnGrid.fields[c.y][c.x].coords = c;
				pl2OwnGrid.fields[c.y][c.x].changeType(FieldType.SHIP);
				pl2OwnGrid.fields[c.y][c.x].setToolTipText(
						pl2OwnGrid.fields[c.y][c.x].coords.owner.type
						+ ": " + c);
			}
		}

		pl1Ships = new JTable(rules.player1.table);
		pl2Ships = new JTable(rules.player2.table);
		pl1Ships.setEnabled(false);
		pl2Ships.setEnabled(false);

	}

	public void startGame() {
		GameBoard.removeAll();
		GameBoard.setLayout(null);
		infoPanel.removeAll();
		infoPanel.setLayout(new FlowLayout());

		infoPanel.add(battleshipPanel);
		infoPanel.validate();
		infoPanel.repaint();

		fireState = true;
		pl1State = true;
		pl2State = true;
		fired = false;

		battleshipPanel.playerShips.setViewportView(pl1Ships);
		battleshipPanel.ChangeViewButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				if (Battleship.this.fireState) {
					Battleship.this.fireState = false;
					Battleship.this.drawMyShips();
					Battleship.this.battleshipPanel.ChangeViewButton.setText("Show opponent ships");
				} else {
					Battleship.this.fireState = true;
					Battleship.this.drawOpponentShips();
					Battleship.this.battleshipPanel.ChangeViewButton.setText("Show my ships");
				}

				if (Battleship.this.currentPlayer == Battleship.this.rules.player1) {
					Battleship.this.pl1State = Battleship.this.fireState;
				} else {
					Battleship.this.pl2State = Battleship.this.fireState;
				}
			}
		});

		battleshipPanel.ChangePlayerButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				Battleship.this.changePlayer();
			}
		});

		JOptionPane.showMessageDialog(parent, "Waiting for "
				+ currentPlayer.getName() + "...", "player changing...",
				JOptionPane.INFORMATION_MESSAGE);
		drawCurrPlayer(true);
	}

	public void drawCurrPlayer(boolean state) {
		parent.gamePhase = (currentPlayer == rules.player1)
				? GamePhase.PLAYING_PLAYER1 : GamePhase.PLAYING_PLAYER2;
		battleshipPanel.PlayerLeftLabel.setText("Player: "
				+ currentPlayer.getName());
		battleshipPanel.ShipInfoLeftLabel.setText("Choose field");
		battleshipPanel.ChangePlayerButton.setEnabled(false);
		battleshipPanel.playerShips.setViewportView(
				(currentPlayer == rules.player1) ? pl2Ships : pl1Ships);

		fired = false;

		if (state) {
			battleshipPanel.ChangeViewButton.setText("Show my ships");
			drawOpponentShips();
		} else {
			battleshipPanel.ChangeViewButton.setText("Show opponent ships");
			drawMyShips();
		}
	}

	public void drawMyShips() {
		GameBoard.removeAll();
		GameBoard.add((currentPlayer == rules.player1)
				? pl1OwnGrid : pl2OwnGrid);
		GameBoard.validate();
		GameBoard.repaint();
	}

	public void drawOpponentShips() {
		GameBoard.removeAll();
		GameBoard.add((currentPlayer == rules.player1)
				? pl2FireGrid : pl1FireGrid);
		GameBoard.validate();
		GameBoard.repaint();
	}

	public void changePlayer() {
		GameBoard.removeAll();
		GameBoard.validate();
		GameBoard.repaint();

		if (parent.gamePhase == GamePhase.GAME_END) {
			currentPlayer = (currentPlayer == rules.player1)
					? rules.player2 : rules.player1;

			battleshipPanel.PlayerLeftLabel.setText(
					currentPlayer.getName() + " ships");

			drawMyShips();

			return;
		}

		if ((currentPlayer == rules.player1)
				&& !(rules.player2 instanceof HumanPlayer)) {
			// computer is playing
			boolean missed = false;
			Coords shot;
			Field fField, oField;

			while (!missed) {
				shot = ((ComputerPlayer) rules.player2).fire(pl1FireGrid);
				assert (shot != null) : "null coords from computershould never";

				fField = pl1FireGrid.fields[shot.y][shot.x];
				oField = pl1OwnGrid.fields[shot.y][shot.x];

				if (oField.fieldType != FieldType.SHIP) {
					if (oField.fieldType == FieldType.NORMAL) {
						oField.changeType(FieldType.MISSED);
						fField.changeType(FieldType.MISSED);
						rules.player2.missed.add(shot);
					} else
						assert false : "wrong coords from computer";

					missed = true;
					drawMyShips();

					JOptionPane.showMessageDialog(parent, rules.player2.getName()
							+ " missed! " + shot, "computer move...",
							JOptionPane.INFORMATION_MESSAGE);
				} else {
					oField.changeType(FieldType.HIT);
					fField.changeType(FieldType.HIT);

					missed = false;
					drawMyShips();

					try {
						fField.coords.owner.hit(fField.coords);
						((ComputerPlayer) rules.player2).shipHitInfo(fField.coords);
						JOptionPane.showMessageDialog(parent, rules.player2.getName()
								+ " hit " + fField.coords.owner.type
								+ "! " + shot, "computer move...",
								JOptionPane.INFORMATION_MESSAGE);
					} catch (ShipSunkException ex) {
						((ComputerPlayer) rules.player2).shipSunkInfo(fField.coords);
						JOptionPane.showMessageDialog(parent,
								fField.coords.owner.type + " sank!", "ship sank!",
								JOptionPane.INFORMATION_MESSAGE);

						markNeighbours(fField.coords.owner, rules.player2);

						rules.player1.table.removeOneShip(
								fField.coords.owner.shipPoints.size());
						pl1Ships.validate();
						pl1Ships.repaint();

						if (isEnd(rules.player2)) {
							gameEnd(rules.player2);
							return;
						}
					}
				}

				pl1State = false;
				fireState = false;
			}
		} else {
			// it's only human
			if (currentPlayer == rules.player1) {
				parent.gamePhase = GamePhase.PLAYING_PLAYER2;
				currentPlayer = rules.player2;
				fireState = pl2State;
			} else {
				parent.gamePhase = GamePhase.PLAYING_PLAYER1;
				currentPlayer = rules.player1;
				fireState = pl1State;
			}

			JOptionPane.showMessageDialog(parent, "Waiting for "
					+ currentPlayer.getName() + "...", "player changing...",
					JOptionPane.INFORMATION_MESSAGE);
		}

		drawCurrPlayer(fireState);
	}

	public void markNeighbours(Ship s, Player cPlayer) {
		Grid markingGrid = (cPlayer == rules.player1)
				? pl2FireGrid : pl1FireGrid;
		for (Coords c : s.shipPoints) {
			List<Field> neighbours = (rules.corneringAllowed)
					? markingGrid.getNeighbours(markingGrid.fields[c.y][c.x])
					: markingGrid.getNeighboursWithCorners(markingGrid.fields[c.y][c.x]);

			for (Field f : neighbours) {
				if (f.fieldType == FieldType.NORMAL) {
					f.changeType(FieldType.INACTIVE);
				}
			}
		}
	}

	public boolean isEnd(Player cPlayer) {
		Collection<Ship> ships = (cPlayer == rules.player1)
				? rules.player2.ships : rules.player1.ships;

		for (Ship s : ships) {
			if (!s.isSunk()) {
				return false;
			}
		}

		return true;
	}

	public void gameEnd(Player cPlayer) {
		pl2FireGrid.validate();
		pl1FireGrid.validate();
		pl2FireGrid.repaint();
		pl1FireGrid.repaint();

		parent.gamePhase = GamePhase.GAME_END;
		currentPlayer = cPlayer;
		battleshipPanel.ChangePlayerButton.setEnabled(true);

		battleshipPanel.ChangeViewButton.setEnabled(false);
		battleshipPanel.ShipInfoLeftLabel.setText(cPlayer.getName()
				+ " wins!");
		battleshipPanel.PlayerLeftLabel.setText(cPlayer.getName()
				+ " ships");

		battleshipPanel.playerShips.setViewportView(null);

		drawMyShips();

		JOptionPane.showMessageDialog(parent,
				"Congratulations! The winner is " + cPlayer.getName() + "!",
				"congratulations!", JOptionPane.INFORMATION_MESSAGE);
	}

	class FireListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			Field f = (Field) e.getSource();
			Battleship bs = Battleship.this;

			if (bs.parent.gamePhase == GamePhase.GAME_END) {
				return;
			}

			if (bs.fired) {
				bs.battleshipPanel.ShipInfoLeftLabel.setText("You have already fired!");
				return;
			}

			if (f.fieldType != FieldType.NORMAL) {
				bs.battleshipPanel.ShipInfoLeftLabel.setText("Wrong field!");
				return;
			}

			Grid currGrid = (bs.currentPlayer == bs.rules.player1)
					? bs.pl2FireGrid : bs.pl1FireGrid;
			Grid opponentOwnGrid = (bs.currentPlayer == bs.rules.player1)
					? bs.pl2OwnGrid : bs.pl1OwnGrid;

			if (opponentOwnGrid.fields[f.row][f.col].fieldType != FieldType.SHIP) {
				f.changeType(FieldType.MISSED);
				bs.currentPlayer.missed.add(new Coords(f.col, f.row, null));
				opponentOwnGrid.fields[f.row][f.col].changeType(FieldType.MISSED);

				f.setToolTipText("Missed " + f);
				bs.battleshipPanel.ShipInfoLeftLabel.setText("You missed!");
				bs.fired = true;
			} else {
				f.changeType(FieldType.HIT);
				opponentOwnGrid.fields[f.row][f.col].changeType(FieldType.HIT);

				f.setToolTipText(f.coords.owner.type + ": " + f);
				bs.battleshipPanel.ShipInfoLeftLabel.setText("You hit "
						+ f.coords.owner.type + "!");
				bs.fired = false;

				try {
					f.coords.owner.hit(f.coords);
				} catch (ShipSunkException ex) {
					JOptionPane.showMessageDialog(parent,
							f.coords.owner.type + " sank!", "ship sank!",
							JOptionPane.INFORMATION_MESSAGE);

					markNeighbours(f.coords.owner, bs.currentPlayer);

					if (bs.currentPlayer == bs.rules.player1) {
						bs.rules.player2.table.removeOneShip(
								f.coords.owner.shipPoints.size());
						bs.pl2Ships.validate();
						bs.pl2Ships.repaint();
					} else {
						bs.rules.player1.table.removeOneShip(
								f.coords.owner.shipPoints.size());
						bs.pl1Ships.validate();
						bs.pl1Ships.repaint();
					}

					if (isEnd(bs.currentPlayer)) {
						gameEnd(currentPlayer);
						return;
					}
				}
			}

			currGrid.validate();
			currGrid.repaint();

			if (fired) {
				bs.battleshipPanel.ChangePlayerButton.setEnabled(true);
			}
		}
	}
}
